using System;
using System.Collections.Generic;
using System.Text;
using System.Net.Sockets;
using SPTraceView.Model;
using System.Net;
using System.IO;
using System.Diagnostics;
using System.Threading;
using Microsoft.Win32;

namespace SPTraceView.Model
{
    internal static class UDPHelper
    {
        public delegate void ProcessTraceEventCallback(
            string serverName,
            ULS.TraceSeverity severity,
            long timeStamp,
            uint processId,
            string exeName,
            string product,
            string category,
            string message,
            Int64 timeCorrection,
            DateTime eventTime);

        private static long s_PacketId = 0;
        private const int SP_TRACE_VIEW_DEFAULT_PORT = 12361;
        internal static int SP_TRACE_VIEW_PORT;
        private static IPAddress MULTICAST_IP_ADDRESS = IPAddress.Parse("232.153.23.255");
        private static IPAddress SP_TRACE_VIEW_IP_ADDRESS = MULTICAST_IP_ADDRESS;
        private static string COMPUTER_NAME = Environment.MachineName;
        private static Guid LOCAL_FARM_ID = SPSafeHelper.LocalFarmId;

        private static int PACKET_VERSION = 1;

        private static ProcessTraceEventCallback s_Callback;
        private static Thread s_ServerThread = null;

        private static object s_SyncRoot = new object();
        private static TraceSettings.FarmRoleSettings s_FarmSettings = new TraceSettings.FarmRoleSettings();
        private static bool s_Running = false;
        private static UdpClient s_UDPClient = null;


        public static void Init(ProcessTraceEventCallback callback, TraceSettings.FarmRoleSettings farmSettings)
        {
            lock (s_SyncRoot)
            {
                bool requiresServerRestart =
                    (s_FarmSettings.m_ServerUseMulticast != farmSettings.m_ServerUseMulticast) ||
                    (s_FarmSettings.m_BroadcastAddress != farmSettings.m_BroadcastAddress) ||
                    (s_FarmSettings.m_FarmRole != farmSettings.m_FarmRole);

                s_Callback = callback;
                s_FarmSettings.CopyFrom(farmSettings);

                if (farmSettings.m_UseMulticastGroup)
                {
                    SP_TRACE_VIEW_IP_ADDRESS = MULTICAST_IP_ADDRESS;
                }
                else
                {
                    SP_TRACE_VIEW_IP_ADDRESS = IPAddress.Parse(farmSettings.m_BroadcastAddress);
                }

                if (requiresServerRestart)
                    EnsureServer();
            }
        }

        internal static void Restart()
        {
            EnsureServer();
        }

        public static void Send(
            ULS.TraceSeverity severity,
            long timeStamp,
            uint processId,
            string exeName,
            string product,
            string category,
            string message,
            DateTime eventTime)
        {
            s_PacketId++;

            try
            {
                byte[] data = null;

                using (MemoryStream memBuff = new MemoryStream())
                using (BinaryWriter wrt = new BinaryWriter(memBuff))
                {
                    wrt.Write(PACKET_VERSION);
                    wrt.Write(LOCAL_FARM_ID.ToString("D"));
                    wrt.Write(COMPUTER_NAME);
                    wrt.Write(s_PacketId);
                    wrt.Write((int)severity);
                    wrt.Write(timeStamp);
                    wrt.Write(processId);
                    wrt.Write(exeName);
                    wrt.Write(product);
                    wrt.Write(category);
                    wrt.Write(message);
                    wrt.Write(eventTime.Ticks);

                    data = memBuff.ToArray();
                }

                lock (s_SyncRoot)
                {
                    UdpClient client = new UdpClient();
                    try
                    {
                        client.Connect(SP_TRACE_VIEW_IP_ADDRESS, SP_TRACE_VIEW_PORT);
                        client.Send(data, data.Length);
                    }
                    finally
                    {
                        client.Close();
                    }
                }
            }
            catch (Exception ex)
            {
                Trace.WriteLine(ex.GetType().ToString() + " : " + ex.Message + "\r\n\r\n" + ex.StackTrace);
            }
        }

        private static void EnsureServer()
        {
            Close();

            if (s_FarmSettings.m_FarmRole == TraceViewFarmRole.Client)
            {
                Trace.WriteLine(string.Format("Startng up a UDP Server to listen for {0} on port {1}.", s_FarmSettings.m_ServerUseMulticast ? "multicasts" : "broadcasts", SP_TRACE_VIEW_PORT.ToString()), "SPTraceView");

                s_ServerThread = new Thread(new ThreadStart(ServerWorker));
                s_ServerThread.SetApartmentState(ApartmentState.STA);
                s_Running = true;
                s_ServerThread.Start();
            }
        }

        public static void Close()
        {
            if (s_ServerThread != null)
            {
                Trace.WriteLine("Shutting down the UDP Server.", "SPTraceView");

                if (s_UDPClient != null &&
                    s_UDPClient.Client != null)
                {
                    s_UDPClient.Client.Shutdown(SocketShutdown.Both);
                    Thread.Sleep(1000);
                }

                s_Running = false;
                Thread.Sleep(1000);

                if (s_UDPClient != null &&
                    s_UDPClient.Client != null)
                {
                    s_UDPClient.Client.Close();
                }

                if (s_ServerThread.IsAlive)
                {
                    s_ServerThread.Abort();
                    s_ServerThread.Join(30000);
                }
                s_ServerThread = null;
            }
        }

        private static void ServerWorker()
        {
            Guid listeningForFarmId = s_FarmSettings.m_UseForeignFarm ? s_FarmSettings.m_ForeignFarmID : LOCAL_FARM_ID;
            string listeningForFarmIdString = listeningForFarmId.ToString("D");

            
            try
            {
                s_UDPClient = new UdpClient(SP_TRACE_VIEW_PORT, AddressFamily.InterNetwork);

                if (s_FarmSettings.m_ServerUseMulticast)
                    s_UDPClient.JoinMulticastGroup(SP_TRACE_VIEW_IP_ADDRESS, 50);

                s_UDPClient.EnableBroadcast = true;
                IPEndPoint ep = new IPEndPoint(s_FarmSettings.m_ServerUseMulticast ? SP_TRACE_VIEW_IP_ADDRESS : IPAddress.Any, SP_TRACE_VIEW_PORT);

                while (s_Running)
                {
                    if (s_UDPClient.Available != 0)
                    {
                        byte[] msg = s_UDPClient.Receive(ref ep);

                        try
                        {
                            using (MemoryStream memBuff = new MemoryStream(msg))
                            using (BinaryReader rdr = new BinaryReader(memBuff))
                            {
                                int version = rdr.ReadInt32();
                                string farmID = rdr.ReadString();

                                if (listeningForFarmIdString.Equals(farmID))
                                {
                                    string computerName = rdr.ReadString();
                                    long pcketID = rdr.ReadInt64();
                                    ULS.TraceSeverity severity = (ULS.TraceSeverity)rdr.ReadInt32();
                                    long timeStamp = rdr.ReadInt64();
                                    uint processId = rdr.ReadUInt32();
                                    string exeName = rdr.ReadString();
                                    string product = rdr.ReadString();
                                    string category = rdr.ReadString();
                                    string message = rdr.ReadString();
                                    DateTime eventTime = new DateTime(rdr.ReadInt64());

                                    if (s_Callback != null)
                                        s_Callback.BeginInvoke(computerName, severity, timeStamp, processId, exeName, product, category, message, 0, eventTime, null, null);

                                    Debug.WriteLine(" ----->>> PACKET ID: >> " + pcketID.ToString() + " << ");
                                }
                            }
                        }
                        catch (Exception ex)
                        {
                            Trace.WriteLine(ex.GetType() + " : " + ex.Message + "\r\n" + ex.StackTrace, "SPTraceView");
                        }
                    }
                }

                Trace.WriteLine("UDP Server worker thread stop request received.", "SPTraceView");
            }
            catch (Exception ex)
            {
                Trace.WriteLine(ex.GetType() + " : " + ex.Message + "\r\n" + ex.StackTrace, "SPTraceView");
            }
            finally
            {
                if (s_FarmSettings.m_ServerUseMulticast)
                    s_UDPClient.DropMulticastGroup(SP_TRACE_VIEW_IP_ADDRESS);

                s_UDPClient.Close();

                Trace.WriteLine("UDP Server worker thread terminated.", "SPTraceView");
            }
        }

        static UDPHelper()
        {
            SP_TRACE_VIEW_PORT = SP_TRACE_VIEW_DEFAULT_PORT;
            try
            {
                RegistryKey key = Registry.CurrentUser.OpenSubKey(@"Software\SharePointInternals\SPTraceView",  
                    RegistryKeyPermissionCheck.Default, 
                    System.Security.AccessControl.RegistryRights.QueryValues);
                if (key != null)
                {
                    int userPort = (int)key.GetValue("Port", SP_TRACE_VIEW_DEFAULT_PORT);
                    if (userPort > 1024 &&
                        userPort <= UInt16.MaxValue)
                    {
                        SP_TRACE_VIEW_PORT = (short)userPort;
                        Trace.WriteLine(string.Format("Using user defined port {0} for UDP communcations.", SP_TRACE_VIEW_PORT), "SPTraceView");
                    }
                    else
                        Trace.WriteLine(string.Format("User port must be between {0} and {1}", 1024, UInt16.MaxValue), "SPTraceView");
                }
            }
            catch (Exception ex)
            {
                Trace.WriteLine(ex.Message + "\r\n" + ex.Source);
            }
        }
    }
}
